spb/datacenterindex
Public
HTML 53.9%
TypeScript 44.5%
JavaScript 0.6%
SQL 0.5%
1import type { CountryDetail, FacilityType } from "@dci/core";2import type { Metadata } from "next";3import Link from "next/link";4import { notFound } from "next/navigation";5import { Breadcrumbs } from "@/components/entity/breadcrumbs";6import { EntityMap } from "@/components/entity/entity-map";7import { JsonLd } from "@/components/entity/json-ld";8import { fPageHref, PagedFacilities, parseFPage } from "@/components/entity/paged-facilities";9import { SectionNav } from "@/components/entity/section-nav";10import { SourcesFooter } from "@/components/entity/sources-footer";11import { EventList } from "@/components/event-list";12import { ClaimsTable } from "@/components/evidence";13import { FacilityTable } from "@/components/facility-table";14import { EnergyContextPanel } from "@/components/intelligence/country/energy";15import { CoverageBar, CoverageStrip, GridConstraintList, HonestEmpty, PillLink, PipelineBars, PublishedOnlyNote, ShareList } from "@/components/intelligence/shared";16import { WatchButton } from "@/components/intelligence/watch-button";17import { ProjectList } from "@/components/project-list";18import { Badge, EstimateMark } from "@/components/ui/badge";19import { Page, Section } from "@/components/ui/card";20import { StackedBar } from "@/components/ui/charts";21import { CountBarList } from "./bars";22import { CountryFlag } from "@/components/ui/country-flag";23import { Metric, MetricCell, MetricRow } from "@/components/ui/metric";24import { EmptyState, ErrorState, OfflineNotice } from "@/components/ui/states";25import { RowLink, TBody, THead, Table, Td, Th, Tr } from "@/components/ui/table";26import { api } from "@/lib/api";27import type { DetailFacilitiesMeta } from "@/lib/dto";28import { fmtCompact, fmtDate, fmtInt, fmtMw, fmtPct, fmtUsd, pluralize, splitMw } from "@/lib/format";29import { OPERATOR_KIND_LABEL, TYPE_LABEL } from "@/lib/labels";30import { routes } from "@/lib/routes";31import { compactJsonLd, NOT_FOUND_METADATA, pageMetadata } from "@/lib/seo";32import type { SearchParams } from "@/lib/sort";33import { GrowthChart } from "./growth-chart";3435export const revalidate = 300;3637type Props = { params: Promise<{ slug: string }>; searchParams: Promise<SearchParams> };3839const HUGE = new Set(["US", "CA", "RU", "CN", "AU", "BR"]);40const SMALL = new Set(["SG", "HK", "LU", "MT", "BH", "QA", "AE", "IL", "NL", "BE", "CH", "IE", "DK", "TW", "KW", "CY", "EE", "LV", "LT", "SI", "PR", "IS"]);41// ≥ 5 so the API returns grid clusters instead of one country-level bubble42const zoomFor = (iso2: string) => (HUGE.has(iso2) ? 5 : SMALL.has(iso2) ? 7 : 5.5);4344function describe(c: CountryDetail): string {45 const bits = [`${fmtInt(c.facilityCount)} data center ${pluralize(c.facilityCount, "facility", "facilities")}`];46 if (c.knownMw) bits.push(`${fmtMw(c.knownMw)} known operational capacity (${fmtPct(c.mwCoverage)} of facilities publish a figure)`);47 const pipe = (c.constructionMw ?? 0) + (c.plannedMw ?? 0);48 if (pipe) bits.push(`≈ ${fmtMw(pipe)} in the pipeline`);49 bits.push(`${fmtInt(c.operatorCount)} ${pluralize(c.operatorCount, "operator")}`);50 bits.push(`${fmtInt(c.cloudRegionCount)} cloud ${pluralize(c.cloudRegionCount, "region")}`);51 if (c.hyperscaleCount) bits.push(`${fmtInt(c.hyperscaleCount)} hyperscale`);52 return `Data centers in ${c.name}: ${bits.join(", ")}. Markets, operators, project pipeline, AI infrastructure, cloud footprint, internet exchanges, energy context and grid constraints — every figure sourced.`;53}5455export async function generateMetadata({ params }: Props): Promise<Metadata> {56 const { slug } = await params;57 const r = await api.country(slug);58 if (!r.ok) return r.status === 404 ? NOT_FOUND_METADATA : { title: "Country", alternates: { canonical: routes.country(slug) } };59 return pageMetadata({ title: `Data Centers in ${r.data.name}`, description: describe(r.data), path: routes.country(r.data.slug) });60}6162function Mw({ mw, estimate }: { mw: number | null | undefined; estimate?: boolean }) {63 const s = splitMw(mw);64 if (!s) return <span className="text-ink-4">—</span>;65 return (66 <>67 {estimate && <EstimateMark className="mr-0.5 text-[0.6em]" />}68 {s.value}69 </>70 );71}72const unitOf = (mw: number | null | undefined) => splitMw(mw)?.unit ?? undefined;7374const IXP_ROWS = 15;7576export default async function CountryPage({ params, searchParams }: Props) {77 const [{ slug }, sp] = await Promise.all([params, searchParams]);78 const fPage = parseFPage(sp.fPage);79 const r = await api.country(slug, fPage > 1 ? { fPage } : undefined);80 if (!r.ok && r.status === 404) notFound();81 if (!r.ok) {82 return (83 <Page className="py-6">84 <OfflineNotice detail={r.error} className="mb-4" />85 <ErrorState />86 </Page>87 );88 }89 const c = r.data;90 const meta = (r.meta ?? {}) as DetailFacilitiesMeta;91 const facTotal = meta.facilitiesTotal ?? c.facilityCount;92 const facPer = meta.facilitiesPerPage ?? 50;93 const base = routes.country(c.slug);94 const statusTotal = Object.values(c.statusBreakdown).reduce((a, b) => a + (b ?? 0), 0);95 const types = (Object.entries(c.typeBreakdown) as Array<[FacilityType, number]>).filter(([, n]) => n > 0).sort((a, b) => b[1] - a[1]);96 const metros = [...c.metros].sort((a, b) => b.facilityCount - a.facilityCount);97 const topOps = [...c.topOperators].sort((a, b) => b.facilityCount - a.facilityCount).slice(0, 10);98 const cloud = [...c.cloudRegions].sort((a, b) => a.provider.name.localeCompare(b.provider.name) || a.code.localeCompare(b.code));99 const ixps = [...(c.ixps ?? [])].sort((a, b) => b.facilityCount - a.facilityCount || a.name.localeCompare(b.name));100 const ixpCount = c.ixpCount ?? ixps.length;101 const aiFacilities = c.aiFacilities ?? [];102 const aiProjects = c.aiProjects ?? [];103 const gridConstraints = c.gridConstraints ?? [];104 const claims = c.claims ?? [];105 const hasAi = aiFacilities.length > 0 || aiProjects.length > 0;106 const facTotalForShare = c.facilityCount || 1;107108 const jsonLd = compactJsonLd({109 "@context": "https://schema.org",110 "@type": "Country",111 name: c.name,112 identifier: c.iso2,113 url: `${routes.country(c.slug)}`,114 description: describe(c),115 geo: c.lat !== null && c.lng !== null ? { "@type": "GeoCoordinates", latitude: c.lat, longitude: c.lng } : undefined,116 });117118 const sections = [119 { id: "map", label: "Map", count: c.facilityCount },120 { id: "growth", label: "Growth" },121 { id: "breakdown", label: "Breakdown", count: statusTotal },122 { id: "markets", label: "Markets", count: metros.length },123 { id: "operators", label: "Operators", count: topOps.length },124 { id: "pipeline", label: "Pipeline" },125 { id: "ai", label: "AI", count: hasAi ? aiFacilities.length + aiProjects.length : 0 },126 { id: "cloud", label: "Cloud", count: cloud.length },127 { id: "connectivity", label: "Connectivity" },128 { id: "projects", label: "Projects", count: c.recentProjects.length },129 { id: "changes", label: "Changes", count: c.recentEvents.length },130 { id: "energy", label: "Energy" },131 { id: "grid", label: "Grid" },132 { id: "coverage", label: "Coverage" },133 { id: "facilities", label: "Facilities", count: facTotal },134 { id: "claims", label: "Claims", count: claims.length },135 ];136137 return (138 <Page>139 <JsonLd data={jsonLd} />140 <div className="pt-3">141 <Breadcrumbs items={[{ name: "Countries", href: routes.countries() }, { name: c.name, href: base }]} />142 </div>143144 <header className="py-5 sm:py-7">145 <div className="flex flex-wrap items-start justify-between gap-3">146 <div className="min-w-0">147 <div className="label-caps">Country · {c.iso2}</div>148 <h1 className="mt-1.5 flex items-center gap-2.5 text-[24px] font-semibold leading-tight tracking-[-0.02em] text-ink sm:text-[30px]">149 <CountryFlag iso2={c.iso2} size={26} />150 <span>Data centers in {c.name}</span>151 </h1>152 <div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-[13px] text-ink-2">153 {(c.region || c.subregion) && <span>{[c.region, c.subregion].filter((v, i, a): v is string => Boolean(v) && a.indexOf(v) === i).join(" · ")}</span>}154 {c.population !== null && (155 <span className="figure text-ink-3" title="Population, latest World Bank vintage">156 {fmtCompact(c.population)} residents157 </span>158 )}159 {c.gdpUsd !== null && (160 <span className="figure text-ink-3" title="GDP, current US$, World Bank">161 {fmtUsd(c.gdpUsd)} GDP162 </span>163 )}164 {(c.population !== null || c.gdpUsd !== null) && <span className="text-[11px] text-ink-4">World Bank</span>}165 </div>166 </div>167 <div className="flex flex-wrap items-center gap-2">168 <WatchButton entityType="country" slug={c.slug} name={c.name} />169 <PillLink href={routes.explore(`entity=facilities&country=${c.iso2}`)}>Explore →</PillLink>170 </div>171 </div>172 </header>173174 <MetricRow cols={5}>175 <MetricCell>176 <Metric value={fmtInt(c.facilityCount)} label="Facilities" note={`${fmtInt(c.operationalCount)} operational`} />177 </MetricCell>178 <MetricCell>179 <Metric value={<Mw mw={c.knownMw} />} unit={unitOf(c.knownMw)} label="Operational MW" note={<CoverageBar value={c.mwCoverage} label="MW coverage" width="w-full max-w-[150px]" />} />180 </MetricCell>181 <MetricCell>182 <Metric value={<Mw mw={c.constructionMw} estimate />} unit={unitOf(c.constructionMw)} label="Under construction" note={`${fmtInt(c.constructionCount)} ${pluralize(c.constructionCount, "site")}${c.constructionMw === null && c.constructionCount ? ", MW not published" : ""}`} />183 </MetricCell>184 <MetricCell>185 <Metric value={<Mw mw={c.plannedMw} estimate />} unit={unitOf(c.plannedMw)} label="Planned MW" note={`${fmtInt(c.plannedCount)} planned ${pluralize(c.plannedCount, "site")}${c.plannedMw === null && c.plannedCount ? ", MW not published" : ""}`} />186 </MetricCell>187 <MetricCell>188 <Metric value={c.announcedInvestmentUsd ? fmtUsd(c.announcedInvestmentUsd) : "—"} label="Announced investment" note={c.announcedInvestmentUsd ? "site-scoped project figures, published" : "not published"} />189 </MetricCell>190 </MetricRow>191 <MetricRow cols={5} className="border-t-0 [&>div]:border-t-0">192 <MetricCell>193 <Metric size="sm" value={fmtInt(c.operatorCount)} label="Operators" />194 </MetricCell>195 <MetricCell>196 <Metric size="sm" value={fmtInt(c.cloudRegionCount)} label="Cloud regions" />197 </MetricCell>198 <MetricCell>199 <Metric size="sm" value={fmtInt(c.hyperscaleCount)} label="Hyperscale" />200 </MetricCell>201 <MetricCell>202 <Metric size="sm" value={fmtInt(c.aiCount)} label="AI facilities" note="confirmed + likely" />203 </MetricCell>204 <MetricCell>205 <Metric size="sm" value={fmtInt(ixpCount)} label="Internet exchanges" />206 </MetricCell>207 </MetricRow>208209 {c.rankings.length > 0 && (210 <div className="-mx-3 mt-3 flex items-center gap-1.5 overflow-x-auto px-3 no-scrollbar sm:mx-0 sm:flex-wrap sm:px-0" aria-label="Ranking positions">211 <span className="label-caps shrink-0">Rankings</span>212 {c.rankings.map((rk) => (213 <Link key={rk.key} href={routes.ranking(rk.key)} className="inline-flex h-10 shrink-0 items-center gap-1.5 whitespace-nowrap rounded-sm border border-hair bg-surface px-2 text-[12px] text-ink hover:bg-surface-2 sm:h-7">214 <span className="figure font-medium">215 #{rk.rank}216 <span className="text-ink-4">/{rk.total}</span>217 </span>218 <span className="text-ink-2">{rk.label}</span>219 </Link>220 ))}221 </div>222 )}223224 <SectionNav items={sections} className="mt-4" />225226 <div className="flex flex-col gap-8 pt-6">227 <Section id="map" title="Map" description="Facilities in the country; hollow rings are city-level locations. Cloud regions are listed below." className="scroll-mt-28">228 <EntityMap center={c.lat !== null && c.lng !== null ? { lat: c.lat, lng: c.lng } : null} zoom={zoomFor(c.iso2)} filters={{ country: c.iso2 }} openLabel={`Open ${c.name} in map`} className="h-[240px] sm:h-[380px]" />229 </Section>230231 <Section id="growth" title="Growth" description="By opening year of indexed facilities. Facilities without a published opening date are not charted." className="scroll-mt-28">232 <GrowthChart data={c.growth} />233 </Section>234235 <Section id="breakdown" title="Breakdown" description="Status and facility type of indexed records." className="scroll-mt-28">236 <div className="grid gap-6 md:grid-cols-2">237 <div>238 <div className="label-caps mb-2">Status</div>239 {statusTotal ? <StackedBar data={c.statusBreakdown} height={12} /> : <EmptyState compact title="No status data" />}240 </div>241 <div>242 <div className="label-caps mb-2">Type</div>243 {types.length ? <CountBarList items={types.map(([t, n]) => ({ label: TYPE_LABEL[t], value: n, href: routes.datacenters(`country=${c.iso2}&type=${t}`) }))} showRank={false} /> : <EmptyState compact title="No type data" />}244 </div>245 </div>246 </Section>247248 {metros.length > 0 && (249 <Section id="markets" title="Top markets" description={`${fmtInt(metros.length)} ${pluralize(metros.length, "metro")} with indexed infrastructure`} className="scroll-mt-28" aside={<Link href={routes.markets()} className="text-[12.5px] text-ink-3 hover:text-ink">All markets →</Link>}>250 <Table dense mobileCollapse={{ total: metros.length, label: "markets" }}>251 <THead>252 <tr>253 <Th>Market</Th>254 <Th numeric>Facilities</Th>255 <Th numeric>Known MW</Th>256 <Th numeric>Pipeline</Th>257 <Th numeric className="hidden sm:table-cell">258 Cloud regions259 </Th>260 <Th numeric className="hidden md:table-cell">261 IXPs262 </Th>263 <Th numeric className="hidden md:table-cell">264 Operators265 </Th>266 </tr>267 </THead>268 <TBody>269 {metros.map((m) => {270 const pipe = m.constructionMw === null && m.plannedMw === null ? null : (m.constructionMw ?? 0) + (m.plannedMw ?? 0);271 return (272 <Tr key={m.id}>273 <Td primary>274 <RowLink href={routes.market(m.slug)}>{m.name}</RowLink>275 {m.regionName && m.regionName !== m.name && <span className="ml-1.5 text-[11.5px] text-ink-3">{m.regionName}</span>}276 </Td>277 <Td label="Facilities" numeric lead>278 {fmtInt(m.facilityCount)}279 </Td>280 <Td label="Known MW" numeric>281 {m.knownMw === null ? <span className="text-ink-4">—</span> : fmtMw(m.knownMw)}282 </Td>283 <Td label="Pipeline" numeric>284 {pipe === null ? (285 <span className="text-ink-4">—</span>286 ) : (287 <>288 <EstimateMark className="mr-0.5" />289 {fmtMw(pipe)}290 </>291 )}292 </Td>293 <Td label="Cloud regions" numeric className="hidden sm:table-cell">294 {fmtInt(m.cloudRegionCount)}295 </Td>296 <Td label="IXPs" numeric className="hidden md:table-cell">297 {fmtInt(m.ixpCount)}298 </Td>299 <Td label="Operators" numeric className="hidden md:table-cell">300 {fmtInt(m.operatorCount)}301 </Td>302 </Tr>303 );304 })}305 </TBody>306 </Table>307 </Section>308 )}309310 {topOps.length > 0 && (311 <Section id="operators" title="Top operators" description={`${fmtInt(c.operatorCount)} ${pluralize(c.operatorCount, "operator")} with facilities in ${c.name}; share of the country's facility count`} className="scroll-mt-28" aside={<Link href={routes.datacenters(`country=${c.iso2}`)} className="text-[12.5px] text-ink-3 hover:text-ink">All facilities →</Link>}>312 <ShareList items={topOps.map((o) => ({ key: o.id, label: <>{o.name}{o.kind && <span className="ml-1.5 text-[11px] text-ink-3">{OPERATOR_KIND_LABEL[o.kind]}</span>}</>, href: routes.operator(o.slug), count: o.facilityCount, share: o.facilityCount / facTotalForShare, mw: o.knownMw ?? null }))} unit="facilities" />313 </Section>314 )}315316 <Section id="pipeline" title="Project pipeline" description="Facilities by lifecycle bucket with the MW they publish, then project records by stage." className="scroll-mt-28" aside={<Link href={routes.projects(`country=${c.iso2}`)} className="text-[12.5px] text-ink-3 hover:text-ink">All projects →</Link>}>317 {c.pipeline ? <PipelineBars pipeline={c.pipeline} /> : <HonestEmpty title="Pipeline breakdown unavailable" />}318 <PublishedOnlyNote className="mt-3" />319 </Section>320321 {hasAi && (322 <Section id="ai" title="AI infrastructure" description="Facilities and projects with confirmed or likely AI / HPC evidence. A single keyword never confirms a site." className="scroll-mt-28" aside={<Link href={routes.ai()} className="text-[12.5px] text-ink-3 hover:text-ink">AI infrastructure index →</Link>}>323 {aiFacilities.length > 0 && (324 <div className="mb-4">325 <div className="label-caps mb-2">Facilities ({fmtInt(aiFacilities.length)})</div>326 <FacilityTable items={aiFacilities} columns={["operator", "market", "status", "mw", "type"]} />327 </div>328 )}329 {aiProjects.length > 0 && (330 <div>331 <div className="label-caps mb-2">Projects ({fmtInt(aiProjects.length)})</div>332 <ProjectList items={aiProjects} />333 </div>334 )}335 </Section>336 )}337338 {cloud.length > 0 && (339 <Section id="cloud" title="Cloud footprint" description={`${fmtInt(cloud.length)} public cloud ${pluralize(cloud.length, "region")} located in ${c.name} — associated with a market, not a specific facility unless a source says so`} className="scroll-mt-28" aside={<Link href={`${routes.cloudRegions()}?country=${c.iso2}`} className="text-[12.5px] text-ink-3 hover:text-ink">All cloud regions →</Link>}>340 <Table dense mobileCollapse={{ total: cloud.length, label: "cloud regions" }}>341 <THead>342 <tr>343 <Th>Provider</Th>344 <Th>Code</Th>345 <Th className="hidden sm:table-cell">Name</Th>346 <Th className="hidden md:table-cell">City</Th>347 <Th numeric>AZs</Th>348 <Th>Status</Th>349 <Th className="hidden lg:table-cell">Launched</Th>350 </tr>351 </THead>352 <TBody>353 {cloud.map((cr) => (354 <Tr key={cr.id}>355 <Td primary>356 <Link href={routes.operator(cr.provider.slug)} className="font-medium text-ink hover:text-accent hover:underline">357 {cr.provider.name}358 </Link>359 <span className="ml-1.5 text-[11.5px] text-ink-3 sm:hidden">{cr.name}</span>360 </Td>361 <Td label="Code">362 <RowLink href={routes.cloudRegion(cr.slug)} className="figure text-[12.5px] font-normal">363 {cr.code}364 </RowLink>365 </Td>366 <Td label="Name" className="hidden sm:table-cell">367 {cr.name}368 </Td>369 <Td label="City" className="hidden md:table-cell" muted>370 {cr.city ?? "—"}371 </Td>372 <Td label="AZs" numeric>373 {cr.availabilityZones === null ? <span className="text-ink-4">—</span> : fmtInt(cr.availabilityZones)}374 </Td>375 <Td label="Status">376 <span className="inline-flex flex-wrap gap-1">377 <Badge tone={cr.status === "operational" ? "ok" : cr.status === "announced" ? "info" : cr.status === "retired" ? "neutral" : "warn"} size="xs">378 {cr.status}379 </Badge>380 {cr.isSovereign && (381 <Badge tone="outline" size="xs" title="Sovereign cloud region">382 Sovereign383 </Badge>384 )}385 </span>386 </Td>387 <Td label="Launched" className="figure hidden text-[12px] text-ink-3 lg:table-cell">388 {cr.launchedOn ? fmtDate(cr.launchedOn) : "—"}389 </Td>390 </Tr>391 ))}392 </TBody>393 </Table>394 </Section>395 )}396397 <Section id="connectivity" title="Connectivity" description={`Internet exchanges with a published presence in ${c.name}'s facilities (PeeringDB and exchange operators).`} className="scroll-mt-28" aside={<Link href={`${routes.ixps()}?country=${c.iso2}`} className="text-[12.5px] text-ink-3 hover:text-ink">All {fmtInt(ixpCount)} IXPs →</Link>}>398 {ixps.length ? (399 <>400 <Table dense mobileCollapse={{ total: Math.min(ixps.length, IXP_ROWS), label: "exchanges" }}>401 <THead>402 <tr>403 <Th>Exchange</Th>404 <Th className="hidden sm:table-cell">City</Th>405 <Th numeric>Networks</Th>406 <Th numeric>Facilities</Th>407 </tr>408 </THead>409 <TBody>410 {ixps.slice(0, IXP_ROWS).map((x) => (411 <Tr key={x.id}>412 <Td primary>413 <RowLink href={routes.ixp(x.slug)}>{x.name}</RowLink>414 {x.nameLong && x.nameLong !== x.name && <span className="ml-1.5 hidden text-[11.5px] text-ink-3 md:inline">{x.nameLong}</span>}415 </Td>416 <Td label="City" className="hidden sm:table-cell" muted>417 {x.city ?? (x.metro?.name ?? "—")}418 </Td>419 <Td label="Networks" numeric>420 {x.networkCount === null ? <span className="text-ink-4" title="Not published">—</span> : fmtInt(x.networkCount)}421 </Td>422 <Td label="Facilities" numeric lead>423 {fmtInt(x.facilityCount)}424 </Td>425 </Tr>426 ))}427 </TBody>428 </Table>429 {ixps.length > IXP_ROWS && (430 <p className="mt-2 text-[11.5px] text-ink-4">431 First {IXP_ROWS} of {fmtInt(ixps.length)} exchanges by facility presence —{" "}432 <Link href={`${routes.ixps()}?country=${c.iso2}`} className="underline decoration-hair-2 hover:text-ink">433 see all434 </Link>435 .436 </p>437 )}438 </>439 ) : (440 <HonestEmpty title="No internet exchange presence recorded" detail="IXP presence comes from published IXP ↔ facility links; none is inferred" />441 )}442 <p className="mt-2 text-[11px] text-ink-4">443 Network counts are shown only when the exchange publishes them.{" "}444 <Link href={routes.connectivity()} className="underline decoration-hair-2 hover:text-ink">445 Connectivity index446 </Link>447 </p>448 </Section>449450 {c.recentProjects.length > 0 && (451 <Section id="projects" title="Recent projects" description="Announced and under-construction developments." className="scroll-mt-28" aside={<Link href={routes.projects(`country=${c.iso2}`)} className="text-[12.5px] text-ink-3 hover:text-ink">All projects →</Link>}>452 <ProjectList items={c.recentProjects} />453 </Section>454 )}455456 {c.recentEvents.length > 0 && (457 <Section id="changes" title="Recent changes" description="Latest detections for facilities and projects in the country." className="scroll-mt-28" aside={<Link href={routes.live(`country=${c.iso2}`)} className="text-[12.5px] text-ink-3 hover:text-ink">Live feed →</Link>}>458 <EventList events={c.recentEvents.slice(0, 10)} dense />459 </Section>460 )}461462 <Section id="energy" title="Power & energy context" description="National electricity indicators for reading capacity figures against the grid." className="scroll-mt-28" aside={<Link href={routes.power()} className="text-[12.5px] text-ink-3 hover:text-ink">Power & grid →</Link>}>463 {c.energy ? <EnergyContextPanel e={c.energy} knownMw={c.knownMw} /> : <HonestEmpty title="No national electricity indicators recorded" />}464 </Section>465466 <Section id="grid" title="Grid constraints" description="Moratoria, grid delays, load caps, new transmission, regulation and large-load queues reported for this country — observed events and curated public reports only, nothing inferred." className="scroll-mt-28">467 <GridConstraintList items={gridConstraints} emptyDetail="no report or grid event has been indexed for this country; that is an absence of reports, not proof of an unconstrained grid" />468 </Section>469470 <Section id="coverage" title="Coverage" description="How much of this country the index actually knows — shown so the figures above can be read honestly." className="scroll-mt-28" aside={<Link href={routes.coverage()} className="text-[12.5px] text-ink-3 hover:text-ink">Coverage report →</Link>}>471 {c.coverage ? <CoverageStrip row={c.coverage} /> : <HonestEmpty title="Coverage not computed" />}472 </Section>473474 <Section id="facilities" title="Facilities" description={`${fmtInt(facTotal)} indexed · sorted by known capacity`} className="scroll-mt-28" aside={<Link href={routes.datacenters(`country=${c.iso2}`)} className="text-[12.5px] text-ink-3 hover:text-ink">Filter & export →</Link>}>475 <PagedFacilities items={c.facilities} total={facTotal} page={fPage} perPage={facPer} hrefFor={(p) => fPageHref(base, sp, p)} columns={["operator", "market", "status", "mw", "type", "confidence"]} />476 </Section>477478 {claims.length > 0 && (479 <Section id="claims" title="Claims about this country" description="Country-scoped figures (national programmes, aggregate capacity statements) — shown as evidence, never summed into facility totals." className="scroll-mt-28">480 <ClaimsTable claims={claims} kind="all" />481 </Section>482 )}483 </div>484485 <SourcesFooter sources={r.sources} note="Country totals are lower bounds computed from published or filed values only; coverage shows the share of facilities with a capacity figure. Population, GDP and electricity: World Bank (CC BY 4.0)." />486 </Page>487 );488}489